﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace nr.Tombola
{
    /// <summary>
    /// Un estrattore di numeri casuali senza ripetizione.
    /// </summary>
    public class NumbersBag : IEnumerable<int>, IEnumerator<int>, IDisposable, ICloneable
    {
        /// <summary>
        /// Costruttore di default.
        /// </summary>
        /// <remarks>Inizializza un sacchetto con 90 numeri (da 1 a 90).</remarks>
        public NumbersBag() : this(90) { }

        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="total">Totale dei numeri da inserire nel sacchetto.</param>
        public NumbersBag(int total)
        {
            Total = total;
            Reset();
        }

        /// <summary>
        /// Distruttore.
        /// </summary>
        ~NumbersBag() { Dispose(false); }

        /// <summary>
        /// Il totale dei numeri inseriti inizialmente nel sacchetto.
        /// </summary>
        public int Total { get; set; }

        /// <summary>
        /// Totale dei numeri restanti nel sacchetto.
        /// </summary>
        public int NumbersLeft { get { return Numbers.Count; } }

        /// <summary>
        /// Il contenuto del sacchetto.
        /// </summary>
        private List<int> Numbers { get; set; }

        /// <summary>
        /// I numeri estratti.
        /// </summary>
        private List<int> Extract { get; set; }

        /// <summary>
        /// Controlla se ci sono numeri nel sacchetto.
        /// </summary>
        public bool HasMoreNumbers { get { return NumbersLeft > 0; } }


        /// <summary>
        /// Mescola i numeri del sacchetto.
        /// </summary>
        /// <remarks>Effettua Count * 1000 scambi.</remarks>
        public void Shuffle()
        {
            Shuffle(NumbersLeft * 1000);
        }

        /// <summary>
        /// Mescola i numeri del sacchetto.
        /// </summary>
        /// <remarks>La procedura scambia di posto i numeri nel sacchetto.</remarks>
        /// <param name="swaps">Numero di scambi casuali di posizione da effettuare.</param>
        public void Shuffle(int swaps)
        {
            Random rnd = new Random(System.Environment.TickCount);
            for (int index = 0; index < swaps; ++index)
            {
                int pos1 = rnd.Next(NumbersLeft);
                int pos2 = rnd.Next(NumbersLeft);
                int temp = Numbers[pos1];
                Numbers[pos1] = Numbers[pos2];
                Numbers[pos2] = temp;
            }
        }

        #region IEnumerable<int> Members

        /// <summary>
        /// Metodo dell'interfaccia IEnumerator.
        /// </summary>
        public IEnumerator<int> GetEnumerator() { return this; }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Metodo dell'interfaccia IEnumerator.
        /// </summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this; }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Distruzione manuale dell'oggetto.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Implementazione effettiva del metodo Dispose (secondo il pattern IDisposable).
        /// </summary>
        protected virtual void Dispose(bool disposing) { }

        #endregion

        #region IEnumerator Members

        /// <summary>
        /// Il numero che attualmente è stato estratto.
        /// </summary>
        public int Current { get; private set; }

        /// <summary>
        /// Reinizializza il sacchetto alla condizione iniziale.
        /// </summary>
        /// <remarks>Reinserisce nel sacchetto tutti i numeri.</remarks>
        public void Reset()
        {
            Numbers = new List<int>(Total);
            Extract = new List<int>(Total);
            for (int counter = 0; counter < Total; )
                Numbers.Add(++counter);
        }

        /// <summary>
        /// Proprietà dell'interfaccia IEnumerable (uguale a "int Current").
        /// </summary>
        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        /// <summary>
        /// Metodo dell'interfaccia IEnumerator.
        /// </summary>
        public bool MoveNext()
        {
            if (Numbers.Count == 0) return false;

            int number = Numbers[0];
            Numbers.RemoveAt(0);
            Extract.Insert(0, number);
            Current = number;
            return true;
        }

        #endregion

        /// <summary>
        /// Controlla se un numero è presente tra i numeri inseriti inizialmente nel sacchetto.
        /// </summary>
        /// <param name="number">Numero da controllare.</param>
        /// <returns>Restituisce True se il numero è presente tra i numeri inseriti inizialmente nel sacchetto.</returns>
        public bool Contains(int number)
        {
            return Numbers.Contains(number);
        }

        /// <summary>
        /// Controlla se un numero è stato già estratto dal sacchetto.
        /// </summary>
        /// <param name="number">Numero da controllare.</param>
        /// <returns>Restituisce True se il numero è già stato estratto.</returns>
        public bool IsExtract(int number)
        {
            return Extract.Contains(number);
        }

        /// <summary>
        /// Forza l'estrazione di un numero.
        /// </summary>
        /// <param name="number">Numero per il quale forzare l'estrazione.</param>
        /// <returns>Restituisce True se è stato possibile forzare l'estrazione.</returns>
        public bool Pull(int number)
        {
            if (Numbers.Contains(number))
            {
                Extract.Insert(0, number);
                Numbers.Remove(number);
                Current = number;
                return true;
            }

            return false;
        }

        /// <summary>
        /// Annulla l'ultima estrazione.
        /// </summary>
        public void Undo()
        {
            if (Extract.Count > 0)
            {
                int number = Extract[0];
                Extract.RemoveAt(0);
                Numbers.Insert(0, number);
            }
        }
        /// <summary>
        /// Reinserisce un numero nel sacchetto.
        /// </summary>
        /// <param name="number">Numero da reinserire nel sacchetto.</param>
        /// <returns>Restituisce un valore booleano che indica se è stato possibile reinserire il numero.</returns>
        /// <remarks>Non è possibile reinserire un numero prima che il numero sia stato estratto.</remarks>
        public bool Push(int number)
        {
            if (!Extract.Contains(number)) return false;

            Extract.Remove(number);
            Numbers.Insert(0, number);
            Current = number;
            return true;
        }

        #region ICloneable Members

        /// <summary>
        /// Clona l'oggetto corrente.
        /// </summary>
        /// <returns>Restituisce l'oggetto clonato.</returns>
        public object Clone()
        {
            NumbersBag result = new NumbersBag();
            result.Total = Total;
            result.Numbers.Clear();
            result.Extract.Clear();
            result.Numbers.AddRange(Numbers);
            result.Extract.AddRange(Extract);
            return result;
        }

        #endregion
    }
}
